浏览器客户端

Ellen Spertus(spertus@google.com

本文档描述了在用户的Web浏览器中运行的App Inventor部分,即appengine子项目的客户端和共享部分。这不包括Blocks Editor代码,它当前在单独的JNLP任务中运行,但正在移动到浏览器中。

背景

Google Web Toolkit

Google App Engine

与App Inventor其他部分的关系

静态依赖

运行时交互

GUI元素

面板

设计标签

MotdBox

PaletteBox

ViewerBox

SourceStructureBox

AssetListBox

PropertiesBox

项目标签

调试选项卡

启动行为

背景

Google Web Toolkit

Google Web Toolkit(GWT)  允许程序员用Java编写客户端 - 服务器应用程序,而不必担心远程过程调用(RPC)的细节,除了为RPC提供显式回调(一个用于成功调用,一个用于失败)。GWT将客户端代码编译为JavaScript,在JavaScript中运行,并且RPC在服务器上作为Java代码运行,通过HTTP完成通信。建议读者 在深入研究运行的App Inventor部分或响应用户浏览器的请求之前了解GWT 

Google App Engine

Google App Engine  (GAE)是一个云计算平台,支持用Java(或Python)编写的程序在Google服务器上运行和维护数据。App Inventor的原始内部版本直接构建在专有的Google基础架构上,但对于开源版本,使用第三方Objectify  数据存储API 重写为使用GAE  

GWT和GAE可以很好地协同工作,GWT服务器可以在GAE服务器上运行。这就是App Inventor的工作方式,如图1所示。

图1:App Inventor客户端和服务器的框图。  
两者都是使用GWT创建的,GWT将前端代码转换为JavaScript,后者在用户的浏览器中使用GWT客户端库运行。后端作为Google App Engine服务在GWT服务器库上运行,使用第三方Objectify API进行数据存储。

与App Inventor其他部分的关系

静态依赖

App Engine客户端YaClientApp依赖于:

运行时交互

图2显示了App Inventor子项目之间的动态交互。当浏览器客户端启动时,各种信息(在 下面的启动行为部分中详细讨论)将从App Engine服务器传递到客户端。这包括服务器构建ID和当天的消息(motd)有关项目组件,资产和用户设置的信息 在两个方向上流动。 

当块编辑器通过Java WebStart文件(未显示)从客户端启动时,块编辑器请求并接收当前项目的组件信息 并 阻止代码在任何更改之后,Blocks Editor会将块代码发送 回服务器。在客户端中添加,删除或重命名 组件时,客户端会将更新的组件信息发送 到服务器,并通知Blocks Editor已对组件进行了更改。然后,块编辑器请求并接收更新的组件信息 从服务器。执行此多步更新是因为只能通过客户端和块编辑器之间的HTTP连接传递少量信息。可以在CodeblocksConnection中找到客户端发送到Blocks Editor的完整消息列表

如果用户建立了从块编辑器到设备(或仿真器)的连接,则块编辑器请求并接收AiPhoneApp(提供“repl”解释器的APK)和当前项目的资产,并将其传递给设备。

如果用户在客户端请求构建应用程序,则客户端向服务器发送请求,该请求将组件信息块代码和密钥库打包 成zip文件,然后将其发送到构建服务器。在尝试构建应用程序之后,构建服务器将状态/错误消息和APK  发送到App Engine服务器,后者保存APK的副本并将其发送给客户端。如果初始请求是构建应用程序并将其下载到手机(而不是生成条形码或将其下载到计算机),则客户端会向Blocks Editor发送消息,然后Blocks Editor会从中请求并接收APK  。 App Engine服务器。

图2:App Inventor子项目之间的运行时交互。不显示数据请求。
客户端发送到Blocks Editor的通知会提醒服务器更新属性,组件,项目或APK。
有关App Engine(浏览器)客户端和服务器的更多详细信息,请参见图1。

GUI元素

面板

主要面板如图3所示。未显示的是RootPanel,它由GWT提供并填充应用程序窗口。其余面板在Ode.initializeUi()中创建。第一个要创建的是mainPanel,它填充了RootPanel。它的孩子类型:

图3:在Ode.initializeUi()中创建的面板,以洋红色标出并标记。  
最外面的面板mainPanel是
DockPanel类型 ,包含三个面板:TopPanelDeckPanelStatusPanel无论视图是“项目”选项卡,“设计”选项卡还是“调试”选项卡,都会显示TopPanel和StatusPanel。图片的其余部分是DeckPanel(人为地减小了尺寸)。它有三个孩子; 显示的那个取决于所选的选项卡。

设计标签

Design选项卡(如图4所示)是用户向项目添加组件,指定其属性和位置以及管理媒体文件(资产)的位置。

图4:“设计”选项卡视图。主VerticalPanel以洋红色显示,方框以蓝色显示,DesignToolbar以深紫色显示。  
此屏幕上有5个框可见:MotdBox,PaletteBox,ViewerBox,SourceStructureBox,PropertiesBox和AssetListBox。底部的StatusPanel已被省略。

图4中突出显示的所有框类都实现了单例模式,  并直接扩展了抽象类com.google.appinventor.client.widgets.boxes.Box,这是一个构建在com.google.gwt.user.client之上的容器小部件。 ui.FlowPanel  ,可自动处理嵌入式窗口小部件的滚动,并可进行调整大小,最小化和恢复。每个盒子都有一个标题,可以在OdeMessages中找到。这些框以私有方法Ode.initializeUi()添加到GUI中。

MotdBox

所述MotdBox  显示当天(MOTD)的消息。在启动期间,方法Ode.setupMotd()调用GetMotdService.getCheckInterval()  RPC以查找检查MOTD更新的频率。一旦检索到响应,客户端就会构造一个MotdFetcher,这会调用它的fetchMotd()方法,从而调用MotdService.getMotd()  请求。检索到MOTD时,调用MotdUi.setMotd()  来显示MOTD。

PaletteBox

所述PaletteBox  包含单个元件,YoungAndroidPalettePanel,一个复合  含有的StackPanel,其中“垂直堆叠其孩子,只有一次一个显示,对于每个子,用户可以点击以显示标题”。在图2中,正在显示“基本”类别。

ViewerBox

所述ViewerBox  包含ProjectEditor  (具体地,YaProjectEditor)。EditorManager维护多个ProjectEditor实例,每个实例对应于当前浏览器会话中打开的每个项目。反过来,每个ProjectEditor都为项目中的每个屏幕都有一个FileEditor。这在图5中说明。

图5还显示了每个YaProjectEditor都引用了YaFormEditor,它管理GWT(拖放和删除)与框和面板之间的大部分通信。例如,如果将新组件拖到YaFormEditor(YaFormEditor.onComponentAdded())上,则SourceStructureBox和PropertiesBox都会收到通知。

为了提供真实组件的“模拟”表示,例如图4中的屏幕,按钮和声音,有一个以MockComponent类为根的模拟组件的层次结构。不可见的组件(如Sound)由MockNonVisibleComponent表示,它具有最小的功能。  MockButton  是更复杂的模拟组件之一,因为它的外观取决于其属性的值,例如Text,Image和BackgroundColor。

图5:ProjectEditor的静态UML类图。有一个EditorManager,它维护一个ProjectEditor实例的集合,每个项目对应于此浏览器会话中打开的项目。每个ProjectEditor都维护一个FileEditor实例集合,一个用于项目中的每个屏幕。ProjectEditor还维护图6中所示的GUI元素。

图6:ViewerBox,GUI组件标记为(左),未标记(右)。  
ViewerBox包含
ScrollBar 中的TabBar  和DeckPanel ,其中包含SimpleNonVisibleComponentsPanel SimpleVisibleComponentsPanel ,其中包含单个VerticalPanel VerticalPanel包含一个CheckBox和一个MockForm ,它包含一个MockForm.PhoneBar,一个MockForm.TitleBar和 一个ScrollPanel中AbsolutePanel 左侧未显示(因为它们不存在于所有项目中)是MockForm中的MockButton  和MockNonVisibleComponent中的MockButton SimpleNonVisibleComponentsPanel

SourceStructureBox

SourceStructureBox中的唯一项 是SourceStructureExplorer,如图6所示,它显示项目组件的分层视图。它被实现为一个  ,其中每个TreeItem  包含一个SourceStructureExplorerItem

AssetListBox

AssetListBox  列出了程序员上传的资产(媒体文件)。它包含一个AssetList,它扩展了Composite  并有一个私有成员assetList,它是一个Tree  ,其中每个TreeItem  包含一个ProjectNode  - 特别是YoungAndroidAssetNode  (图7)。CommandRegistry中指定的节点被选中时可用的上下文菜单命令DeleteFileCommand  和DownloadFileCommand

图7:ProjectNode的直接和间接子类。  
未显示的是由YoungAndroidProjectNode实现的接口HasAssetFolder <YoungAndroidAssetsFolder>。

PropertiesBox

与其他框一样,PropertiesBox是在Ode.initializeUi()中创建的,尽管在选择组件之前不会填充它。具体来说,构造YaFormEditor时,它会创建一个新的PropertiesPanel并保存名为designProperties的私有引用。发生以下任何情况时,将更新此面板的内容:

项目标签

Projects选项卡(如图8所示)是用户创建,删除,打开,上载或下载项目和密钥库的位置。ProjectToolbar位于顶部,包含四个ToolbarItem实例,每个实例都有一个名称(例如,“DownloadAll”),一个标题(例如,“Download All Projects”和一个Command(例如,ProjectToolbar.NewAction)。)ProjectListBox由ProjectList填充,其中包含一个表,其中包含可用于排序的“Name”和“Date Created”编辑器,以及包含CheckBox,项目名称和创建日期的行。

图8:项目选项卡视图。它由一个包含ProjectToolbar(洋红色)的VerticalPanel和一个包含ProjectListBox的Horizo​​ntalPanel组成。ProjectToolbar扩展了工具栏,它扩展了Composite,由一个包含两个Horizo​​ntalPanels的Horizo​​ntalPanel组成,名为leftButtons和RightButtons(当前未使用)。ProjectListBox填充了ProjectList,它扩展了Composite,包含一个包含Grid的VerticalPanel ,其“Name”和“Date Created”标题是Horizo​​ntalPanels,每个都支持在任一方向排序。每行包括一个的复选框,一个标签  包含项目名称和标签  包含该项目的创建日期

调试选项卡

仅当AppInventorFeatures.hasDebuggingView()返回true时,才会提供“调试”选项卡(图8)。它由一个包含两个框的WorkAreaPanel组成。第一个是MessagesOutputBox,它显示传递给MessagesOutput.addMessages()的消息,这些消息是关于构建应用程序并将其下载到手机的请求的状态。OdeLogBox显示通过以下静态方法之一记录的消息的输出:

图9:调试选项卡视图。在上面的框中,MessagesOutputBox显示来自BuildServer的消息。  
下方框OdeLogBox显示客户端代码中生成的日志消息。

图10:Debugging选项卡组件的静态UML类图。
UI元素以蓝色显示。未显示(因为它们不是App Inventor代码)是ColumnLayout.Column的超类,
它是
AbstractIndexedDropController,以及Widget的超类,它是UIObject。  
MessagesOutputBox和OdeLogBox都是Box的子类。

启动行为

图11:启动信息流和控制流程。  
RPC在客户端(左)和服务器(右)之间用水平箭头表示,其中查询和响应具有相同的颜色和初始编号。彩色虚线箭头表示在收到相关响应后发生的本地方法和构造函数调用。黑色虚线表示由于听众而导致的本地方法调用。例如,Ode.onModuleLoad()生成一个RPC(2Q)。收到响应后,它会发出三个本地调用:UserSettings.loadSettings(),ProjectManager()和Ode.initializeUi(),它们调用Ode.setupMotd()。

图11显示了用户开始App Inventor会话时的信息和控制流程(假设没有RPC故障或其他异常事件)。请注意,文档的这一部分比其他部分更容易改变。

  1. 用户在浏览器中导航到App Inventor服务器的URL,该服务器将HTTP GET请求[1Q]发送到App Engine服务器(由appengine项目生成)。
  2. App Engine服务器返回客户端JavaScript代码[1R]。
  3. 客户端浏览器开始在Ode.onModuleLoad()中运行代码,该代码调用UserInfoService.getUserInformation()RPC以获取有关用户[2Q]的基本信息。
  4. 服务器返回用户数据对象[2R],包括登录用户的唯一标识符,电子邮件地址以及用户是否已接受服务条款(TOS)。(以下讨论假定用户已经接受了TOS。如果没有,则RPC失败,客户端将用户重定向到他们可以查看和接受TOS的页面。)回调在Ode.onModuleLoad()内,它调用以下方法/构造函数:
  1. UserSettings.loadSettings(),它调用UserInfoService.loadUserSettings()RPC来查找用户[3Q]打开的最后一个项目的id。(因为所有请求都是异步的,所以客户端会在收到响应之前进入下一步。)
  2. ProjectManager(),它调用ProjectService.getProjectInfos()RPC来获取有关所有用户项目的基本信息[4Q]。
  3. Ode.initializeUi(),用于设置浏览器内应用程序的用户界面。然后调用Ode.setupMotd(),它调用GetMotdService.getCheckInterval()RPC来查找检查当天消息(MOTD)[5Q]的更新的频率。

为了便于讨论,我们假设查询3Q,4Q和5Q按照它们的发布顺序进行回答,但响应可以按任何顺序进行。

  1. RPC UserInfoService.loadUserSettings()返回一个JSON编码的字符串,其中包含用户的设置,包括上次打开的项目[3R]的ID。这将启动一系列本地方法调用。当项目被添加到客户端的已知项目列表时,最后一个Ode.openProject()在本地注册一个侦听器以调用Ode.openYoungAndroidProjectInDesigner(),这将在上面的步骤4b中作为查询4Q的结果发生。
  2. RPC ProjectService.getProjectInfos()返回List <UserProject>,其中包含有关每个项目的摘要信息,例如其id,名称和最近修改日期[4R]。对于其中的每一个,都会调用ProjectManager.addProject(),它会调用任何已注册的侦听器,即步骤5中等待添加用户最新项目的侦听器。
  3. 调用Ode.openYoungAndroidProjectDesigner(),它本身调用Project.loadProjectNodes()并调用RPC ProjectServices.getProject()以获取有关上次打开的项目[6Q]的完整信息。
  4. 服务器响应GetMotdService.getCheckInterval(),其中客户端在检查MOTD [5R]的更改之间应等待的秒数。客户端构造一个MotdFetcher(),它会调用它的fetchMotd()方法,调用MotdService.getMotd()来请求MOTD [7Q]。(对MOTD的后续请求被添加到其他RPC请求中;请参阅MotdFetcher.onStart()。)
  5. 服务器响应查询6Q,提供有关用户上次打开的项目[6R]的完整信息,Project.fireProjectLoaded()调用Ode.openYoungAndroidProjectDesigner(),打开此项目的Designer视图。
  6. 最后,服务器返回MOTD [7R],MotdUi.setMotd()方法将其写入用户界面。

后续控制流和RPC取决于用户的行为。

原文